package com.lagou.edu.factory;

import com.lagou.edu.annotation.Autowired;
import com.lagou.edu.annotation.Repository;
import com.lagou.edu.annotation.Service;
import com.mysql.jdbc.StringUtils;

import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author 刘强
 * <p>
 * 工厂类，生产对象（使用反射技术）
 */
public class AnnotationBeanFactory {
    /**
     * 任务一：读取指定文件夹，通过反射技术实例化对象并且存储待用（map集合）
     * 任务二：对外提供获取实例对象的接口（根据id获取）
     * 使用三级缓存，来处理循环依赖问题
     */
    private static Map<String, Object> singletonObjects = new HashMap<>();  // 三级缓存
    private static Map<String, Object> earlySingletonObjects = new HashMap<>();  // 二级缓存
    private static Map<String, Object> registeredSingletons = new HashMap<>();  // 一级缓存
    private static Map<String, Class> singletonFactories = new HashMap<>();  // 记录class对象
    // 基础扫描包
    private static final String BASEPACKAGE = "com.lagou.edu";
    // 扫描包下的Java类
    private static List<String> classPaths = new ArrayList<>();

    static {
        try {
            init();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 初始化
     */
    public static void init() throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException {
        // 包名
        String basePack = BASEPACKAGE;
        // 先把包名转换为路径,首先得到项目的classpath
        String classpath = AnnotationBeanFactory.class.getResource("/").getPath();// 类路径是/
        //System.out.println("classpath：" + classpath);
        // 然后把我们的包名basPach转换为路径名
        basePack = basePack.replace(".", File.separator);
        // 然后把classpath和basePack合并
        String searchPath = classpath + basePack;
        doPath(new File(searchPath));// 类路径是\
        // 这个时候我们已经得到了指定包下所有的类的绝对路径了。我们现在利用这些绝对路径和java的反射机制得到他们的类对象
        for (String s : classPaths) {
            // 把 D:\work\code\20170401\search-class\target\classes\com\baibin\search\a\A.class 这样的绝对路径转换为全类名com.baibin.search.a.A
            //System.out.println(s);
            // 替换D:\work\code\20170401\search-class\target\classes\com\baibin\search
            s = s.replace(classpath.replace("/", "\\").replaceFirst("\\\\", ""), "")
                    .replace("\\", ".")
                    .replace(".class", "");
            //System.out.println(s);
            if (s.endsWith("TransferServlet")) {
                continue;
            }
            Class cls = Class.forName(s);
            // 加载对象
            loadClass2map(cls);
        }

        // 处理依赖
        handlerDependent();
    }

    // 处理依赖
    private static void handlerDependent() throws IllegalAccessException, NoSuchFieldException, ClassNotFoundException {
        // 处理依赖(全部都放入三级缓存)
        for (String key : singletonObjects.keySet()) {
            Class clazz = singletonFactories.get(key);
            Object instance = singletonObjects.get(key);

            // 依赖字段处理
            dependentHandler(clazz, instance);

            // 依赖都已处理，放入二级缓存
            earlySingletonObjects.put(key, instance);
        }

        // 清除三级缓存数据
        singletonObjects.clear();

        // 处理二级缓存

        // 将二级缓存放入三级缓存
        registeredSingletons.putAll(earlySingletonObjects);
    }

    // 依赖字段处理
    private static void dependentHandler(Class clazz, Object obj) throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
        Field[] fields = clazz.getDeclaredFields();// 获取指定的私有字段
        for (int i = 0; i < fields.length; i++) { // 遍历循环
            if (fields[i].isAnnotationPresent(Autowired.class)) {
                // 获取依赖对象引用
                Autowired dept = fields[i].getAnnotation(Autowired.class);
                String val = dept.value();
                String autoField = fields[i].getName();// 获取变量名称
                if (StringUtils.isNullOrEmpty(val)) {
                    val = autoField;// 获取变量名称
                }

                boolean flag = false;
                Object cashObj = null;

                // 依赖对象在三级缓存中
                String id = upperCase(val);
                if (singletonObjects.containsKey(id)) {
                    cashObj = singletonObjects.get(id);
                    flag = true;
                }
                // 依赖对象在二级缓存中
                else if (earlySingletonObjects.containsKey(id)) {
                    cashObj = earlySingletonObjects.get(id);
                    flag = true;
                }
                // 依赖对象在一级缓存中
                else if (registeredSingletons.containsKey(id)) {
                    cashObj = registeredSingletons.get(id);
                    flag = true;
                }

                if (flag) {
                    // 反射 pojo类处理
                    Field declaredField = clazz.getDeclaredField(autoField);// 获取指定的私有字段
                    declaredField.setAccessible(true);// 暴力访问成员属性

                    // 设置依赖
                    declaredField.set(obj, cashObj);
                } else {
                    throw new ClassNotFoundException("依赖对象没有找到：" + autoField);
                }
            }
        }
        // 依赖处理完毕
    }

    /**
     * 首字母大写
     */
    public static String upperCase(String str) {
        char[] ch = str.toCharArray();
        if (ch[0] >= 'a' && ch[0] <= 'z') {
            ch[0] = (char) (ch[0] - 32);
        }
        return new String(ch);
    }

    // 加载指定注解类到容器中
    private static void loadClass2map(Class clazz) throws IllegalAccessException, InstantiationException, NoSuchFieldException, ClassNotFoundException {
        // 判断类是否存在注解 @Service @Repository
        if (clazz.isAnnotationPresent(Service.class)) {
            handlerServiceAnnotation(clazz);
        } else if (clazz.isAnnotationPresent(Repository.class)) {
            handlerRepositoryAnnotation(clazz);
        }
    }

    // 处理@Service注解
    private static void handlerServiceAnnotation(Class clazz) throws IllegalAccessException, InstantiationException, NoSuchFieldException, ClassNotFoundException {
        Service service = (Service) clazz.getAnnotation(Service.class);
        String id = service.value();
        if (StringUtils.isNullOrEmpty(id)) {
            id = clazz.getSimpleName();// 例如：ConnectionUtils
        }
        id = upperCase(id);// 首字母大写

        // 创建实例
        Object instance = clazz.newInstance();

        // 先放入三级缓存
        singletonObjects.put(id, clazz.newInstance());

        // 记录class对象
        singletonFactories.put(id, clazz);
    }

    // 处理@Repository注解
    private static void handlerRepositoryAnnotation(Class clazz) throws IllegalAccessException, InstantiationException, NoSuchFieldException, ClassNotFoundException {
        Repository service = (Repository) clazz.getAnnotation(Repository.class);
        String id = service.value();
        if (StringUtils.isNullOrEmpty(id)) {
            id = clazz.getSimpleName();// 例如：ConnectionUtils
        }

        // 创建实例
        Object instance = clazz.newInstance();

        // 先放入三级缓存
        singletonObjects.put(id, instance);

        // 记录class对象
        singletonFactories.put(id, clazz);
    }

    /**
     * 该方法会得到所有的类，将类的绝对路径写入到classPaths中
     */
    public static void doPath(File file) {
        if (file.isDirectory()) {//文件夹
            //文件夹我们就递归
            File[] files = file.listFiles();
            for (File f1 : files) {
                doPath(f1);
            }
        } else {//标准文件
            //标准文件我们就判断是否是class文件
            if (file.getName().endsWith(".class")) {
                //如果是class文件我们就放入我们的集合中。
                classPaths.add(file.getPath());
            }
        }
    }

    // 任务二：对外提供获取实例对象的接口（根据id获取）
    public static Object getBean(String id) {
        if (id != null) {
            id = upperCase(id);
        }
        return registeredSingletons.get(id);
    }

    public static void main(String[] args) {
        Object connectionUtils = AnnotationBeanFactory.getBean("ConnectionUtils");

        System.out.println(registeredSingletons.size());
        for (String key : registeredSingletons.keySet()) {
            System.out.println("key:" + key + " ,class:" + registeredSingletons.get(key));
        }

        System.out.println("----------------------------------------------");
        System.out.println(connectionUtils);
    }

}
